//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.Authentication
{
    using System;
    using System.Collections;
    using System.Collections.ObjectModel;
    using System.DirectoryServices;
    using System.Globalization;
    using System.Management;
    using System.Text;
    using System.Security.Permissions;

    public class LocalAuthentication : BaseAuthentication
    {
        private const string FULLNAME = "FullName";

        private const string WINNTPROVIDER = "WinNT://";

        /// <summary>
        /// Get all Members of DirectoryEntry
        /// </summary>
        /// <param name="searchEntry">DirectoryEntry</param>
        /// <returns>Object</returns>
        private static Object GetAllMembers(DirectoryEntry searchEntry)
        {
            object members = searchEntry.Invoke("Groups", null);
            return members;
        }

        /// <summary>
        /// Get the DirectoryEntry
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        private static DirectoryEntry GetObject(ICredentials credentials, string filter)
        {
            DirectoryEntry root = new DirectoryEntry(WINNTPROVIDER + credentials.DomainName);
            root.Children.SchemaFilter.Add(filter);
            DirectoryEntry directoryEntry = null;
            try
            {
                directoryEntry = root.Children.Find(credentials.UserName);
                if (!directoryEntry.SchemaClassName.Equals(filter, StringComparison.OrdinalIgnoreCase))
                {
                    directoryEntry = null;
                }
            }
            catch (Exception)
            {
                //Catch & consume
            }

            return directoryEntry;
        }

        /// <summary>
        /// Get the User(ICredentials) from the Local Network
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override ICredentials GetUser(ICredentials credentials)
        {
            ICredentials userCredential = credentials;
            userCredential.Name = string.Format(CultureInfo.InvariantCulture, "{0}\\{1}", credentials.DomainName, credentials.UserName);
            DirectoryEntry userResult = LocalAuthentication.GetObject(credentials, CredentialType.User.ToString());
            if (userResult != null)
            {
                if (userResult.Properties[FULLNAME].Value != null)
                {
                    userCredential.DisplayName = userResult.Properties[FULLNAME].Value.ToString();
                }
            }
            else
            {
                userCredential = null;
            }

            return userCredential;
        }

        /// <summary>
        /// Get the Group(ICredentials) from the Local Network
        /// </summary>
        /// <param name="credentials">ICredentials</param>
        /// <returns>ICredentials</returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override ICredentials GetGroup(ICredentials credentials)
        {
            ICredentials groupCredential = credentials;
            DirectoryEntry userResult = LocalAuthentication.GetObject(credentials, CredentialType.Group.ToString());
            if (userResult != null)
            {
                string groupName = BaseAuthentication.GetPropertyValue(userResult, NAME);
                groupCredential = new Credentials(credentials.DomainName + "\\" + groupName, groupName, credentials.DomainName, CredentialType.Group);
            }
            else
            {
                groupCredential = null;
            }

            return groupCredential;
        }

        /// <summary>
        /// Get all Groups for the Local User
        /// </summary>
        /// <param name="credentials"></param>
        /// <returns></returns>
        [EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
        public override Collection<string> GetGroups(ICredentials credentials)
        {
            Collection<string> groups = new Collection<string>();
            try
            {
                ConnectionOptions Conn = new ConnectionOptions();
                string targethost = Environment.MachineName;
                Conn.Timeout = TimeSpan.FromSeconds(2);
                string scopeStr = string.Format(CultureInfo.InvariantCulture, "\\\\{0}\\root\\cimv2", targethost);
                ManagementScope scope = new ManagementScope(scopeStr, Conn);
                scope.Connect();

                ObjectQuery groupQuery = new ObjectQuery(string.Format(CultureInfo.InvariantCulture, "SELECT Name FROM Win32_Group WHERE Domain='{0}'", targethost));

                ManagementObjectSearcher groupSearcher = new ManagementObjectSearcher(scope, groupQuery);
                ManagementObjectCollection groupQueryCollection = groupSearcher.Get();

                foreach (ManagementObject groupObject in groupQueryCollection)
                {
                    string groupName = groupObject["Name"].ToString();
                    StringBuilder memberQs = new StringBuilder();
                    memberQs.Append("SELECT GroupComponent from Win32_GroupUser where PartComponent = \"Win32_UserAccount.Domain='");
                    memberQs.Append(credentials.DomainName);
                    memberQs.Append("',Name='");
                    memberQs.Append(credentials.UserName);
                    memberQs.Append("'\" AND GroupComponent = \"Win32_Group.Domain='");
                    memberQs.Append(targethost);
                    memberQs.Append("',Name='");
                    memberQs.Append(groupName);
                    memberQs.Append("'\"");

                    ObjectQuery query = new ObjectQuery(memberQs.ToString());
                    ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query);
                    ManagementObjectCollection queryCollection = searcher.Get();

                    foreach (ManagementObject memberObject in queryCollection)
                    {
                        String[] names = memberObject["GroupComponent"].ToString().Split(',');
                        string user = names[0].Substring(names[0].IndexOf("=", StringComparison.OrdinalIgnoreCase) + 1).Replace("\"", " ").Trim() + "\\" +
                            names[1].Substring(names[1].IndexOf("=", StringComparison.OrdinalIgnoreCase) + 1).Replace("\"", " ").Trim();
                        groups.Add(user);
                    }
                }
            }
            catch
            {
                // Ignore: We will return no groups present for the user.
            }

            return groups;
        }
    }
}
